package com.qinglei.recoup.longconn.service;

import com.qinglei.recoup.common.exception.RecoupException;
import com.qinglei.recoup.longconn.constant.LongConnConstant;
import com.qinglei.recoup.longconn.entity.DeviceFileInfo;
import com.qinglei.recoup.longconn.entity.Message;
import com.qinglei.recoup.longconn.entity.MessageV4_1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 文件存储策略抽象类
 */
@Slf4j
public abstract class FileStorage implements DisposableBean {

    protected Map<String, DeviceFileInfo> userDataFileMap = new ConcurrentHashMap<>();
    protected Map<String, DeviceFileInfo> userDataFileMapV4 = new ConcurrentHashMap<>();
    protected Map<String, DeviceFileInfo> userDataFileMapCom = new ConcurrentHashMap<>();
    protected Map<String, DeviceFileInfo> rawDataFileMap = new ConcurrentHashMap<>();
    protected Map<String, DeviceFileInfo> rawDataFileMapV4 = new ConcurrentHashMap<>();
    protected Map<String, DeviceFileInfo> rawDataFileMapCom = new ConcurrentHashMap<>();
    protected String dataFilePath;
    protected Long fileSplitSize;
    protected Long dataBufSize;

    protected Lock fileInfoLock = new ReentrantLock();

    /**
     * 文件切分类型
     */
    protected int fileSplitType = LongConnConstant.FILE_SPLIT_TYPE_TIME;

    protected Map<Integer, String> fileTypeMap;

    protected OssService ossService;


    /**
     * 保存设备上传数据
     * @param msg
     */
    abstract public void save(Message msg);


    /**
     * 保存设备上传数据 4.1版本
     * @param msg
     */
    abstract public void save(MessageV4_1 msg);

    abstract public void save(Object message);

    /**
     * 设备下线，删除设备
     * @param sn
     */
    abstract public void procDeviceOffline(String sn);

    protected boolean flushData(DeviceFileInfo deviceFileInfo, boolean isForceUpload) {
        // 按设备加锁
        Lock lock = deviceFileInfo.getLock();
        lock.lock();
        boolean ret = false;
        try {
            ByteBuf buf = deviceFileInfo.getBuf();
            String fileName = deviceFileInfo.getFileName();
            if (buf.readableBytes() > 0) {
                ByteBuf writeBuf = Unpooled.buffer(buf.readableBytes());
                buf.readBytes(writeBuf);
                write(this.dataFilePath + fileName, writeBuf.array());
                buf.clear();

            }

            File uploadFileTmp = new File(this.dataFilePath + fileName);
            if(uploadFileTmp.exists() && uploadFileTmp.length()>0){
                if (isForceUpload || this.fileSplitType == LongConnConstant.FILE_SPLIT_TYPE_SIZE) {
                    if (this.ossService != null) {
                        ossService.uploadFile(fileName, this.dataFilePath + fileName);
                    }
                }
            }

            ret = true;
        } catch (Exception e) {
            log.error("flushData error", e);
        } finally {
            lock.unlock();
        }
        return ret;
    }

    protected long write(String filePath, byte[] data) throws RecoupException, IOException {
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            log.info("create dir: {}", file.getParentFile().getAbsolutePath());
            if (!file.getParentFile().mkdirs()) {
                throw new RecoupException("create dir failed:" + file.getParentFile().getAbsolutePath());
            }
        }
        if (!file.exists()) {
            boolean newFile = file.createNewFile();
            log.info("create file: {}", filePath);
            if (!newFile) {
                throw new RecoupException("create file failed: " + filePath);
            }
        }

        long fileSize = 0;
        try (FileOutputStream fos = new FileOutputStream(file, true);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            bos.write(data);
            bos.flush();
            fileSize = file.length();
            log.info("write file complete, file:{} fileSize: {}", filePath, fileSize);
            return fileSize;
        } catch (Exception e) {
            log.error("write file error", e);
        }
        return fileSize;
    }

    /**
     * 对象销毁时写数据
     *
     * @param deviceFileInfo
     */
    protected void writeDataByDestroy(DeviceFileInfo deviceFileInfo) {
        Lock lock = deviceFileInfo.getLock();
        ByteBuf buf = deviceFileInfo.getBuf();
        String fileName = deviceFileInfo.getFileName();
        // 按设备加锁
        lock.lock();
        try {
            if (buf.readableBytes() > 0) {
                ByteBuf writeBuf = Unpooled.buffer(buf.readableBytes());
                buf.readBytes(writeBuf);
                write(this.dataFilePath + fileName, writeBuf.array());
                buf.release();
            }
        } catch (Exception e) {
            log.error("write file error", e);
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    @Override
    public void destroy() {
        log.info("destroy run");
        fileInfoLock.lock();
        try {
            this.userDataFileMap.forEach((key, val) -> writeDataByDestroy(val));
            this.userDataFileMapV4.forEach((key, val) -> writeDataByDestroy(val));
            this.userDataFileMapCom.forEach((key, val) -> writeDataByDestroy(val));
            this.rawDataFileMap.forEach((key, val) -> writeDataByDestroy(val));
            this.rawDataFileMapV4.forEach((key, val) -> writeDataByDestroy(val));
            this.rawDataFileMapCom.forEach((key, val) -> writeDataByDestroy(val));
        } catch (Exception e) {
            log.error("destroy error", e);
        } finally {
            fileInfoLock.unlock();
        }
    }

}
